package com.base.commons.tools.redis;

import com.alibaba.ttl.TransmittableThreadLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * 创建redis操作类RedisGlobalLock(自定义)
 * redis提供RedisTemplate方法
 * redis提供三个方法：
 * (1)lock  获取锁并锁定   本方法是立即获取锁状态，如果获取成功并锁定，如果获取失败
 * (2)tryLock   尝试获取锁并锁定  本方式是在指定时间尝试获取锁
 * (3)unlock  释放锁   当业务处理完毕必须释放锁
 * 重点:
 * lock和tryLock区别：lock是实时获取,tryLock是尝试在一段时间内一直在获取
 */
@Component
public class RedisGlobalLock {
    Logger logger = LoggerFactory.getLogger(RedisGlobalLock.class);

    /** 单个业务持有锁的时间10s，防止死锁 */
    private final static long LOCK_EXPIRE     = 30 * 1000L;

    private ThreadLocal<Map<String,Long>> local = new TransmittableThreadLocal<Map<String,Long>>();
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void put(String key, Long val) throws IllegalArgumentException {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        Map<String, Long> oldMap = local.get();
        if (oldMap == null) {
            Map<String, Long> newMap = new HashMap<>();
            newMap.put(key, val);
            local.set(newMap);
        } else {
            oldMap.put(key, val);
        }
    }

    public void remove(String key) {
        if (key == null) {
            return;
        }
        Map<String, Long> oldMap = local.get();
        if (oldMap!=null) {
            oldMap.remove(key);
        }
        if (CollectionUtils.isEmpty(oldMap)) {
            local.remove();
        }
    }

    public Long get(String key) {
        final Map<String, Long> map = local.get();
        if ((map != null) && (key != null)) {
            return map.get(key);
        } else {
            return null;
        }
    }

    /**
     * 获取锁
     * @param key        锁Key
     * @return          是否获取锁
     */
    public boolean lock(String key) {
        long newValue = System.currentTimeMillis()+TimeUnit.MILLISECONDS.convert(LOCK_EXPIRE, TimeUnit.SECONDS);
        return getLock(key, newValue, LOCK_EXPIRE, TimeUnit.MILLISECONDS);
    }

    /**
     * 获取锁
     * @param key        锁Key
     * @param expire      有效期
     * @param expireUnit   有效期时间单位
     * @return          是否获取锁
     */
    public boolean lock(String key, long expire, TimeUnit expireUnit) {
        long newValue = System.currentTimeMillis()+TimeUnit.MILLISECONDS.convert(expire, expireUnit);
        return getLock(key, newValue, expire, expireUnit);
    }

    /**
     * 获取锁
     * @param key        锁Key
     * @param value      锁Value
     * @param expire      有效期
     * @param expireUnit   有效期时间单位
     * @return          是否获取锁
     */
    public boolean lock(String key, long value, long expire, TimeUnit expireUnit) {
        return getLock(key, value, expire, expireUnit);
    }

    /**
     * 释放锁
     * @param key  锁Key
     */
    public void unlock(String key) {
        Long value = get(key);
        if(value!=null) {
            redisTemplate.delete(key);
            logger.debug("释放redis锁成功,key:{}",key);
            remove(key);
        }
    }

    /**
     * 获取锁
     * @param key     锁键
     * @param value     锁值
     * @param time    全局锁生命周期
     * @param unit    时间单位
     * @return       是否获取到锁
     */
    public boolean getLock(String key,long value,long time, TimeUnit unit) {
        try {
            Boolean isOk = redisTemplate.opsForValue().setIfAbsent(key, value,time,unit);
            if(!isOk) {
                return false;
            }

            // 获得锁
            redisTemplate.expire(key, time, unit);
            put(key,value);
            logger.debug("获取redis锁成功,key:{}",key);
            return true;
        } catch (Exception e) {
            logger.error("获取redis锁异常",e);
            return false;
        }
    }
}
